package cn.song.docflow.common;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import lombok.extern.slf4j.Slf4j;

import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.BufferedReader;
import java.util.*;
import java.util.stream.Collectors;

import static cn.hutool.json.JSONUtil.toJsonStr;

/**
 * 全局异常处理
 *
 * @author SongRenShuo
 */
@Slf4j
@ResponseBody
@RestControllerAdvice
public class CommonAbnormal {

    private static final String REQUIRED_REQUEST_BODY_IS_MISSING = "Required request body is missing";
    private static final String CONTENT_TYPE = "Content type";
    private static final String NOT_SUPPORTED = "not supported";
    private static final String NO_MULTIPART_BOUNDARY_WAS_FOUND = "no multipart boundary was found";

    /**
     * 异常处理程序 - 处理自定义业务异常
     *
     * @param e       自定义异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException e, HttpServletRequest request) {
        String msg = "业务异常:" + e.getMsg();
        logError(e, request, msg);
        return R.failed(Convert.toInt(e.getCode(), -1), e.getMsg(), null);
    }

    /**
     * 异常处理程序 - 处理不支持的HTTP请求方法异常
     *
     * @param e       HTTP请求方法不支持异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<String> handlerHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        String msg = "请求方式错误，期望为：" + e.getSupportedHttpMethods();
        logError(e, request, msg);
        return R.failed(msg);
    }

    /**
     * 异常处理程序 - 处理multipart请求异常
     *
     * @param e       multipart异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(MultipartException.class)
    public R<String> handlerMultipartException(MultipartException e, HttpServletRequest request) {
        String msg = "请求解析失败：Content-Type格式错误，可能使用了错误的请求格式";

        // 检查具体错误原因
        if (StrUtil.contains(e.getMessage(), NO_MULTIPART_BOUNDARY_WAS_FOUND)) {
            msg = "Content-Type错误：接口期望接收JSON数据但收到了form-data格式请求";
        }

        logError(e, request, msg);
        return R.failed(msg);
    }


    /**
     * 异常处理程序 - 处理不支持的HTTP媒体类型异常
     *
     * @param e       HTTP媒体类型不支持异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public R<String> handlerHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e, HttpServletRequest request) {
        // 获取当前请求尝试使用的 Content-Type
        MediaType contentType = e.getContentType();
        String contentTypeStr = (contentType != null) ? contentType.toString() : "未知";

        // 获取服务器支持的 Content-Type 列表
        List<MediaType> supportedMediaTypes = e.getSupportedMediaTypes();
        String supportedTypesStr = supportedMediaTypes.stream()
                .map(MediaType::toString)
                .collect(Collectors.joining(", "));

        // 构建更友好的错误消息
        String msg = String.format(
                "请求头 Content-Type '%s' 不被支持。请使用以下支持的类型之一: [%s]",
                contentTypeStr,
                supportedTypesStr
        );

        logError(e, request, msg);
        return R.failed(msg);
    }

    /**
     * 异常处理程序 - 处理数据库操作相关的非瞬态异常
     *
     * @param e       数据库操作异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     *//*
    @ExceptionHandler(NonTransientDataAccessException.class)
    public R<String> handlerSqlException(NonTransientDataAccessException e, HttpServletRequest request) {
        String msg = "";
        Throwable cause = e.getCause();
        if (cause instanceof SQLIntegrityConstraintViolationException) {
            msg = "数据插入失败,可能违反了完整性约束";
        } else if (cause instanceof SQLSyntaxErrorException) {
            msg = "SQL语句语法错误";
        }
        logError(e, request, "数据库操作错误：" + msg);
        return R.failed("数据库错误,请重试,超过三次失败,请尽快联系管理员：" + msg);
    }*/

    /**
     * 异常处理程序 - 处理绑定异常（如请求参数校验失败）
     *
     * @param e       绑定异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(BindException.class)
    public R<String> handlerBindException(BindException e, HttpServletRequest request) {

        List<String> errorMessageList = e.getAllErrors().stream()
                .map(ObjectError::getDefaultMessage)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        String msg = "请求参数格式有误：" + errorMessageList;
        logError(e, request, msg);
        return R.failed(msg);
    }


    /**
     * 异常处理程序 - 处理方法参数类型不匹配异常
     *
     * @param e       方法参数类型不匹配异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R<String> handlerMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        String msg = "请求参数类型不匹配";
        // 如果是HttpMessageNotReadableException，也可能是由于日期转换失败导致的
        if (e.getCause() instanceof MethodArgumentTypeMismatchException) {
            MethodArgumentTypeMismatchException nestedException = (MethodArgumentTypeMismatchException) e.getCause();
            msg += "（嵌套）：无法将值 " + nestedException.getValue() + " 转换为类型 " + Objects.requireNonNull(nestedException.getRequiredType()).getSimpleName();
            logError(e, request, msg);
            return R.failed(msg);
        }
        msg += "：无法将值 " + e.getValue() + " 转换为类型 " + Objects.requireNonNull(e.getRequiredType()).getSimpleName();
        logError(e, request, msg);
        return R.failed(msg);

    }

    /**
     * 异常处理程序 - 处理约束违反异常（如参数校验失败）
     *
     * @param e       约束违反异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public R<String> handlerConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
        // 校验框架 - violation
        String msg = "请求参数错误：";
        StringBuilder errorMessage = new StringBuilder(msg);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        for (ConstraintViolation<?> violation : violations) {
            String messageTemplate = violation.getMessageTemplate();
            // // 大多数情况下错误信息会包含冒号分隔的字段名和错误描述
            if (messageTemplate.contains(":")) {
                errorMessage.append(violation.getMessage().split(":")[1].trim()).append(", ");
            } else {
                errorMessage.append(violation.getMessage()).append(" ");
            }
        }
        // 去除最后一个逗号和空格
        errorMessage.setLength(errorMessage.length());
        logError(e, request, msg + errorMessage);
        return R.failed(errorMessage.toString());
    }

    /**
     * 异常处理程序 - 处理HTTP消息不可读异常
     *
     * @param e       HTTP消息不可读异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R<String> handlerHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
        String msg = "请求体缺失或格式错误";
        Throwable cause = e.getCause();
        // 获取原始消息用于备用检查
        String originalMessage = e.getMessage();

        // 优先处理具体的根异常类型 (Cause)，并提取详细信息
        if (cause instanceof JsonParseException) {
            JsonParseException jsonParseException = (JsonParseException) cause;
            // 提供更具体的 JSON 解析错误信息，例如行号、列号
            String locationInfo = "";
            if (jsonParseException.getLocation() != null) {
                locationInfo = " (位置: 第 " + (jsonParseException.getLocation().getLineNr() + 1) +
                        " 行, 第 " + (jsonParseException.getLocation().getColumnNr() + 1) + " 列)";
            }
            msg = "JSON 格式错误：" + jsonParseException.getOriginalMessage() + locationInfo;
        } else if (cause instanceof InvalidFormatException) {
            InvalidFormatException invalidFormatException = (InvalidFormatException) cause;
            // 提供导致转换失败的值和目标类型信息
            Object invalidValue = invalidFormatException.getValue();
            Class<?> targetType = invalidFormatException.getTargetType();
            msg = "请求体中的数据格式错误。值 '" + invalidValue + "' 无法转换为目标类型 '" +
                    (targetType != null ? targetType.getSimpleName() : "未知类型") + "'。";
        } else if (cause instanceof MismatchedInputException) {
            MismatchedInputException mie = (MismatchedInputException) cause;
            // 增强字段名提取逻辑
            String fieldName = "未知字段";
            String targetType = Optional.ofNullable(mie.getTargetType())
                    .map(Class::getSimpleName)
                    .orElse("未知类型");

            // 尝试从路径中获取字段名
            if (mie.getPath() != null && !mie.getPath().isEmpty()) {
                // 获取路径中的最后一个引用，这通常是最具体的字段
                JsonMappingException.Reference lastRef = mie.getPath().get(mie.getPath().size() - 1);
                if (lastRef.getFieldName() != null) {
                    fieldName = lastRef.getFieldName();
                } else if (lastRef.getIndex() >= 0) {
                    fieldName = "索引为 " + lastRef.getIndex() + " 的元素";
                } else {
                    fieldName = lastRef.getDescription();
                }
            }

            msg = "数据类型不匹配：字段 '" + fieldName + "' 期望类型为 '" + targetType + "'";
        } else if (cause instanceof org.springframework.beans.TypeMismatchException) {
            // 注意：这里检查的是 Spring 的 TypeMismatchException，不是 Jackson 的
            org.springframework.beans.TypeMismatchException typeMismatchException = (org.springframework.beans.TypeMismatchException) cause;
            // 提供属性名和期望的类型信息
            String propertyName = typeMismatchException.getPropertyName();
            Class<?> requiredType = typeMismatchException.getRequiredType();
            // 获取失败的值
            Object invalidValue = typeMismatchException.getValue();

            StringBuilder sb = new StringBuilder("请求体中的字段类型不匹配。");
            if (propertyName != null) {
                sb.append("字段 '").append(propertyName).append("' ");
            }
            sb.append("期望类型为 '").append(requiredType != null ? requiredType.getSimpleName() : "未知类型").append("', ");
            sb.append("但收到的值为 '").append(invalidValue).append("' (类型为 ").append(invalidValue != null ? invalidValue.getClass().getSimpleName() : "null").append(")。");
            msg = sb.toString();
        }
        // 如果根异常不是上面的特定类型，则检查 HttpMessageNotReadableException 本身或其消息
        else if (originalMessage != null) {
            // 检查原始消息内容的备用方案
            if (originalMessage.contains(REQUIRED_REQUEST_BODY_IS_MISSING)) {
                msg = "请求体缺失，请检查是否传递了JSON参数";
            } else if (originalMessage.contains(CONTENT_TYPE) && originalMessage.contains(NOT_SUPPORTED)) {
                msg = "请求头中的 Content-Type 必须为 application/json";
            }
        }

        logError(e, request, msg);
        return R.failed(msg);
    }

    /**
     * 异常处理程序 - 处理所有未被捕获的异常
     *
     * @param e       通用异常对象
     * @param request 当前HTTP请求对象
     * @return 统一返回格式的错误信息
     */
    @ExceptionHandler(Throwable.class)
    public R<String> handlerException(Throwable e, HttpServletRequest request) {
        String msg = "全局异常处理异常捕获";
        logError(e, request, msg);
        String message = StrUtil.length(e.getMessage()) > 100 ? "系统异常,请尽快联系管理员" : e.getCause() == null ? e.getMessage() : e.getCause().getMessage();
        return R.failed("操作失败：" + message);
    }

    /**
     * 错误日志打印 - 记录异常详细信息
     *
     * @param e       异常对象
     * @param request 当前HTTP请求对象
     * @param msg     异常描述信息
     */
    private static void logError(Throwable e, HttpServletRequest request, String msg) {
        HashMap<String, Object> formData = getAllFormDataParameters(request);
        String body = getAllBodyParameters(request);
        log.error("{} 请求路径：{} formData参数：{} body参数：{} 错误原因：{}", msg, request.getRequestURI(), toJsonStr(formData), body, e.getMessage(), e);
    }


    /**
     * 获取request中所有formData参数
     *
     * @param request 当前HTTP请求对象
     * @return 包含所有formData参数的Map
     */
    public static HashMap<String, Object> getAllFormDataParameters(HttpServletRequest request) {
        HashMap<String, Object> formData = new HashMap<>(16);
        try {
            // 获取所有参数名
            Enumeration<String> e = request.getParameterNames();
            // 通过Enumeration类中的hasMoreElements()判断是否还有参数名
            while (e.hasMoreElements()) {
                // 获取当前参数名
                String parameterName = e.nextElement();
                // 再通过request.getParameter("")的方法来获取对应参数名的值
                formData.put(parameterName, request.getParameter(parameterName));
            }
        } catch (Exception e) {
            log.error("获取request中所有formData参数 异常：{}", e.getMessage());
        }
        return formData;
    }


    /**
     * 获取request中所有Body参数
     *
     * @param request 当前HTTP请求对象
     * @return 包含所有Body参数的字符串
     */
    public static String getAllBodyParameters(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try {
            try (BufferedReader br = request.getReader()) {
                String line;
                while ((line = br.readLine()) != null) {
                    // 添加换行符，保持原始格式
                    sb.append(line).append('\n');
                }
            }
        } catch (Exception e) {
            log.error("获取request中所有Body参数 异常：{}", e.getMessage());
        }
        return sb.toString();
    }

}


